#include "DXUT.h"
#include "RenderBRDFModels.h"
#include "..\\UIControlsDef.h"
#include "..\\IO\\ObjFileLoader.h"
extern float g_fExpoParam;
extern float g_fGammaParam;

CRenderBRDFModels::CRenderBRDFModels(void):CRenderStaticMesh()
{
	m_pEMLighting = new CRenderEMCubeMap();
	m_pRenderAssistLine = new CRenderAssistLine();
	m_curBRDFType = BRDFType::DIFFUSE;
	m_ListBRDFTypesCombo = NULL;
	m_BRDFNamesArray.RemoveAll();
	std::wstring str1(L"LAFORTUNE");
	m_BRDFNamesArray.Add(str1);
	std::wstring str2(L"WARD");
	m_BRDFNamesArray.Add(str2);
	std::wstring str3(L"SV_LAFORTUNE");
	m_BRDFNamesArray.Add(str3);
	std::wstring str4(L"DIFFUSE");
	m_BRDFNamesArray.Add(str4);
	std::wstring str5(L"MIRROR");
	m_BRDFNamesArray.Add(str5);

	//Gen random sequences!!!
	m_iNumRamdomSamples = 100;
	m_pRandomNumGen = new CGenRandomSeq(SequenceType::FOLDED_HAMMERSLEY, m_iNumRamdomSamples);
	m_pRandomNumGen->genSequence();
	m_fCxy = -1.0f; m_fCz = 1.0f; m_fCn = 1.2f;
	m_pRandomNumGen->genLafortuneSamples(m_fCxy,m_fCz,powf(10.0f,m_fCn));
	m_fAx = 0.1f; m_fAy = 0.1f;
	m_pRandomNumGen->genWardSamples(m_fAx,m_fAy);

	m_pRenderWardBRDFTech = NULL;
	m_pRenderLafortuneBRDFTech = NULL;
	m_pRenderMirrorTech = NULL;
}

CRenderBRDFModels::~CRenderBRDFModels(void)
{
	SAFE_DELETE(m_pEMLighting);
	SAFE_DELETE(m_pRenderAssistLine);
	SAFE_DELETE(m_pRandomNumGen);
}

void CRenderBRDFModels::InitEffectsVariables()
{
	m_pRenderTech = m_pEffect->GetTechniqueByName("RenderBRDFModels");
	m_pRenderWardBRDFTech = m_pEffect->GetTechniqueByName("RenderWardBRDF");
	m_pRenderLafortuneBRDFTech = m_pEffect->GetTechniqueByName("RenderLafortuneBRDF");
	m_pRenderMirrorTech = m_pEffect->GetTechniqueByName("RenderMirrorBall");

	m_pLafortuneVecs = m_pEffect->GetVariableByName("smplsVecArray")->AsVector();
	m_pLafortuneScales = m_pEffect->GetVariableByName("fs")->AsScalar();
	m_pLafortuneLobe = m_pEffect->GetVariableByName("lobe")->AsVector();
	m_pWardVecs = m_pEffect->GetVariableByName("smplsVecArray")->AsVector();
	m_pWardScales = m_pEffect->GetVariableByName("fs")->AsScalar();

	m_pEyePt = m_pEffect->GetVariableByName("eyePoint")->AsVector();
	m_pCubeMapSR = m_pEffect->GetVariableByName("EMTex")->AsShaderResource();
	m_pfExpo = m_pEffect->GetVariableByName("fExpo")->AsScalar();
	m_pfGamma = m_pEffect->GetVariableByName("fGamma")->AsScalar();
}

void CRenderBRDFModels::ReloadEffectsDebug(ID3D11Device* pd3dDevice, CDirectoryUtil* pDirUtil)
{
	HRESULT hr = S_OK;
	SAFE_RELEASE(m_pEffect);
	//create effects
	TCHAR ShaderFileDir[MAX_PATH];
	enum SUBDIRECTORY subfolder = SUBDIRECTORY::SHADERSDIR;
	pDirUtil->GetAbsoluteFileFolder(L"RenderBRDFModels.fx",subfolder, ShaderFileDir);
	V(CD3DUtilityFunction::CreateEffectsFromFile(pd3dDevice, ShaderFileDir, NULL, "fx_5_0", &m_pEffect));	
	InitEffectsVariables();

	//reload assistline effect
	m_pRenderAssistLine->ReloadEffectsDebug(pd3dDevice,pDirUtil);	
}

HRESULT CRenderBRDFModels::OnD3D11CreateDevice(ID3D11Device* pd3dDevice, 
		const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext)
{
	HRESULT hr = S_OK;

    if(!m_pModelRender||!m_pModelRender->isModelReady())
	{
			printf("Please load the model firstly!!!\n");
			return E_FAIL;
	}
	//create effects
	TCHAR ShaderFileDir[MAX_PATH];
	enum SUBDIRECTORY subfolder = SUBDIRECTORY::SHADERSDIR;
	pDirUtil->GetAbsoluteFileFolder(L"RenderBRDFModels.fx",subfolder, ShaderFileDir);
	V_RETURN(CD3DUtilityFunction::CreateEffectsFromFile(pd3dDevice, ShaderFileDir, NULL, "fx_5_0", &m_pEffect));
	InitEffectsVariables();
	//call createdevice function for mesh
    V_RETURN(m_pModelRender->OnD3D11CreateDevice(pd3dDevice, m_pRenderTech, pBackBufferSurfaceDesc, pDirUtil, pUserContext));
	//call createdevice for EM Lighting
	V_RETURN(m_pEMLighting->OnD3D11CreateDevice(pd3dDevice, pBackBufferSurfaceDesc, pDirUtil, pUserContext));
	//call createdevice for assist line
	//fill assist line points array
	if(m_pRenderAssistLine->m_bPtsReady)
		V_RETURN(m_pRenderAssistLine->OnD3D11CreateDevice(pd3dDevice,pBackBufferSurfaceDesc,pDirUtil,pUserContext));
	return hr;
}

HRESULT CRenderBRDFModels::OnD3D11ResizedSwapChain(ID3D11Device* pd3dDevice, 
		IDXGISwapChain* pSwapChain, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, CDirectoryUtil* pDirUtil, void* pUserContext)
{
	HRESULT hr = S_OK;
	if(m_pModelRender)
		V_RETURN(m_pModelRender->OnD3D11ResizedSwapChain(pd3dDevice,pSwapChain,pBackBufferSurfaceDesc,pDirUtil,pUserContext));	
	
	if(m_pEMLighting)
		V_RETURN(m_pEMLighting->OnD3D11ResizedSwapChain(pd3dDevice,pSwapChain,pBackBufferSurfaceDesc,pDirUtil,pUserContext));

	if(m_pRenderAssistLine)
		V_RETURN(m_pRenderAssistLine->OnD3D11ResizedSwapChain(pd3dDevice,pSwapChain,pBackBufferSurfaceDesc,pDirUtil,pUserContext));

	return hr;
}

void CRenderBRDFModels::OnD3D11ReleasingSwapChain(void* pUserContext)
{
	if(m_pModelRender)
		m_pModelRender->OnD3D11ReleasingSwapChain(pUserContext);

	if(m_pEMLighting)
		m_pEMLighting->OnD3D11ReleasingSwapChain(pUserContext);

	if(m_pRenderAssistLine)
		m_pRenderAssistLine->OnD3D11ReleasingSwapChain(pUserContext);
}

void CRenderBRDFModels::OnD3D11DestroyDevice(void* pUserContext)
{
	if(m_pModelRender)
		m_pModelRender->OnD3D11DestroyDevice(pUserContext);

	if(m_pEMLighting)
		m_pEMLighting->OnD3D11DestroyDevice(pUserContext);

	if(m_pRenderAssistLine)
		m_pRenderAssistLine->OnD3D11DestroyDevice(pUserContext);

	//remove UIs
	RemoveGUIComp();
}
void CRenderBRDFModels::OnD3D11FrameRender(ID3D11Device* pd3dDevice, ID3D11DeviceContext* pd3dImmediateContext, 
		CBaseCamera* pViewCamera, CDirectoryUtil* pDirUtil, double fTime, float fElapsedTime, void* pUserContext)
{
	if(!m_pModelRender||!m_pModelRender->isModelReady())
	{
		printf("Please load the model firstly!!!\n");
		return;
	}
	//render EM firstly!
	m_pEMLighting->OnD3D11FrameRender(pd3dDevice,pd3dImmediateContext,pViewCamera,pDirUtil,fTime,fElapsedTime,pUserContext);

	HRESULT hr = S_OK;
	// Get the projection & view matrix from the camera class
	CModelViewerCamera* pCurCamera = static_cast<CModelViewerCamera*>(pViewCamera);
	D3DXMATRIX mWorld = *(pCurCamera->GetWorldMatrix());
	D3DXMATRIX mView = *(pCurCamera->GetViewMatrix());
	D3DXMATRIX mProj = *(pCurCamera->GetProjMatrix());
	D3DXMATRIX mViewProjection = mView * mProj;
	hr = (m_pEffect->GetVariableByName("mViewProjection")->AsMatrix())->SetMatrix((float*)(&mViewProjection));
	//set eyept
	m_pEyePt->SetFloatVector((float*)(pViewCamera->GetEyePt()));

	//update sh lighting
	UpdateLightingEnvironment(pViewCamera);
	//effect parameters
	hr = m_pCubeMapSR->SetResource(m_pEMLighting->GetD3D10EnvironmentMapRV());
	hr = m_pfExpo->SetFloat(g_fExpoParam);
	hr = m_pfGamma->SetFloat(g_fGammaParam);
	switch(m_curBRDFType)
	{
	case BRDFType::DIFFUSE:
		{
		m_pModelRender->Render(pd3dDevice,pd3dImmediateContext,m_pEffect,m_pRenderTech,RENDERMESHTECH::RENDERSTATICMESH,mWorld,
				fTime,fElapsedTime,pUserContext);
		}
		break;
	case BRDFType::MIRROR:
		{
			m_pModelRender->Render(pd3dDevice,pd3dImmediateContext,m_pEffect,m_pRenderMirrorTech,RENDERMESHTECH::RENDERSTATICMESH,mWorld,
				fTime,fElapsedTime,pUserContext);
		}
		break;
	case BRDFType::LAFORTUNE:
		{
			m_pLafortuneVecs->SetFloatVectorArray((float*)(m_pRandomNumGen->LafortuneSmpls.GetData()),0,m_pRandomNumGen->m_iNumLafortuneSmpls);
			m_pLafortuneScales->SetFloatArray(m_pRandomNumGen->LafortuneScales.GetData(),0,m_pRandomNumGen->m_iNumLafortuneSmpls);
			D3DXVECTOR4 lobe = D3DXVECTOR4(m_fCxy, m_fCxy, m_fCz, powf(10.f,m_fCn));
			m_pLafortuneLobe->SetFloatVector((float*)&lobe);
			m_pModelRender->Render(pd3dDevice,pd3dImmediateContext,m_pEffect,m_pRenderLafortuneBRDFTech,RENDERMESHTECH::RENDERSTATICMESH,mWorld,
				fTime,fElapsedTime,pUserContext);
		}
		break;
	case BRDFType::WARD:
		{
			m_pWardVecs->SetFloatVectorArray((float*)(m_pRandomNumGen->WardSmpls.GetData()),0,m_pRandomNumGen->m_iNumWardSmpls);
			m_pWardScales->SetFloatArray(m_pRandomNumGen->WardScales.GetData(),0,m_pRandomNumGen->m_iNumWardSmpls);
			m_pModelRender->Render(pd3dDevice,pd3dImmediateContext,m_pEffect,m_pRenderWardBRDFTech,RENDERMESHTECH::RENDERSTATICMESH,mWorld,
				fTime,fElapsedTime,pUserContext);
		}
		break;
	case BRDFType::SV_LAFORTUNE:
		{

		}
		break;
	}
	

	//render assist line
	if(m_pRenderAssistLine->m_bPtsReady)
		m_pRenderAssistLine->OnD3D11FrameRender(pd3dDevice,pd3dImmediateContext,pViewCamera,pDirUtil,fTime,fElapsedTime,pUserContext);	
}

void CRenderBRDFModels::AddGUIComp(CDXUTDialog* pControlHud, CDXUTDialog* pControlSampleUI, int iX, int& iY, int iYo, int iWidth, int iHeight)
{
	if(pControlSampleUI != NULL)
		m_pCurControlDlg = pControlSampleUI;
	else if(pControlHud != NULL)
		m_pCurControlDlg = pControlHud;
	//lafortune
	m_pCurControlDlg->AddComboBox(IDC_COMBOBOX_BRDFTYPE,0,iY+=iYo,iWidth+20,iHeight,0,false,&m_ListBRDFTypesCombo);
	m_ListBRDFTypesCombo->SetDropHeight( 64 );
	for(unsigned int i = 0; i < m_BRDFNamesArray.GetSize();i++)		
		m_ListBRDFTypesCombo->AddItem(m_BRDFNamesArray[i].data(),NULL);
	m_ListBRDFTypesCombo->SetSelectedByIndex( m_curBRDFType );

	int iYtmp = iY;
	m_pCurControlDlg->AddStatic(IDC_STATIC_CXY,L"c_xy:-1.00f",0,iYtmp+=iYo,iWidth,iHeight);
	m_pCurControlDlg->AddSlider(IDC_SLIDER_CXY,0,iYtmp+=iYo,iWidth,iHeight,0,210,5);
	m_pCurControlDlg->AddStatic(IDC_STATIC_CZ,L"c_z:1.00f",0,iYtmp+=iYo,iWidth,iHeight);
	m_pCurControlDlg->AddSlider(IDC_SLIDER_CZ,0,iYtmp+=iYo,iWidth,iHeight,0,100,100);
	m_pCurControlDlg->AddStatic(IDC_STATIC_N,L"n:1.20f",0,iYtmp+=iYo,iWidth,iHeight);
	m_pCurControlDlg->AddSlider(IDC_SLIDER_N,0,iYtmp+=iYo,iWidth,iHeight,100,320,120);	
	//ward
	iYtmp = iY;
	m_pCurControlDlg->AddStatic(IDC_STATIC_AX,L"a_x:0.1f",0,iYtmp+=iYo,iWidth,iHeight);
	m_pCurControlDlg->AddSlider(IDC_SLIDER_AX,0,iYtmp+=iYo,iWidth,iHeight,1,200,100);	
	m_pCurControlDlg->AddStatic(IDC_STATIC_AY,L"a_y:0.1f",0,iYtmp+=iYo,iWidth,iHeight);
	m_pCurControlDlg->AddSlider(IDC_SLIDER_AY,0,iYtmp+=iYo,iWidth,iHeight,1,200,100);	
		
	//
	UpdateGUIComponents(m_pCurControlDlg);	
}

void CRenderBRDFModels::RemoveGUIComp()
{
	if(!m_pCurControlDlg)
		return;
	m_pCurControlDlg->RemoveControl(IDC_COMBOBOX_BRDFTYPE);
	m_pCurControlDlg->RemoveControl(IDC_STATIC_CXY);
	m_pCurControlDlg->RemoveControl(IDC_SLIDER_CXY);
	m_pCurControlDlg->RemoveControl(IDC_STATIC_CZ);
	m_pCurControlDlg->RemoveControl(IDC_SLIDER_CZ);
	m_pCurControlDlg->RemoveControl(IDC_STATIC_N);
	m_pCurControlDlg->RemoveControl(IDC_SLIDER_N);

	m_pCurControlDlg->RemoveControl(IDC_STATIC_AX);
	m_pCurControlDlg->RemoveControl(IDC_SLIDER_AX);
	m_pCurControlDlg->RemoveControl(IDC_STATIC_AY);
	m_pCurControlDlg->RemoveControl(IDC_SLIDER_AY);
}

void CRenderBRDFModels::UpdateGUIComponents(CDXUTDialog* pControlDlg)
{
	//set all controls invisible
	pControlDlg->GetControl(IDC_STATIC_CXY)->SetVisible(false);
	pControlDlg->GetControl(IDC_SLIDER_CXY)->SetVisible(false);
	pControlDlg->GetControl(IDC_STATIC_CZ)->SetVisible(false);
	pControlDlg->GetControl(IDC_SLIDER_CZ)->SetVisible(false);
	pControlDlg->GetControl(IDC_STATIC_N)->SetVisible(false);
	pControlDlg->GetControl(IDC_SLIDER_N)->SetVisible(false);

	pControlDlg->GetControl(IDC_STATIC_AX)->SetVisible(false);
	pControlDlg->GetControl(IDC_SLIDER_AX)->SetVisible(false);
	pControlDlg->GetControl(IDC_STATIC_AY)->SetVisible(false);
	pControlDlg->GetControl(IDC_SLIDER_AY)->SetVisible(false);

	switch(m_curBRDFType)
	{
	case BRDFType::LAFORTUNE:
		{
			pControlDlg->GetControl(IDC_STATIC_CXY)->SetVisible(true);
			pControlDlg->GetControl(IDC_SLIDER_CXY)->SetVisible(true);
			pControlDlg->GetControl(IDC_STATIC_CZ)->SetVisible(true);
			pControlDlg->GetControl(IDC_SLIDER_CZ)->SetVisible(true);
			pControlDlg->GetControl(IDC_STATIC_N)->SetVisible(true);
			pControlDlg->GetControl(IDC_SLIDER_N)->SetVisible(true);
		}
		break;
	case BRDFType::WARD:
		{
			pControlDlg->GetControl(IDC_STATIC_AX)->SetVisible(true);
			pControlDlg->GetControl(IDC_SLIDER_AX)->SetVisible(true);
			pControlDlg->GetControl(IDC_STATIC_AY)->SetVisible(true);
			pControlDlg->GetControl(IDC_SLIDER_AY)->SetVisible(true);
		}
		break;
	case BRDFType::SV_LAFORTUNE:
		{

		}
		break;
	case BRDFType::DIFFUSE:
		{

		}
		break;
	case BRDFType::MIRROR:
		{

		}
		break;
	}


}

void CRenderBRDFModels::OnGUIEvent( UINT nEvent, int nControlID, CDXUTControl* pControl, void* pUserContext )
{
	switch( nControlID )
	{
		case IDC_COMBOBOX_BRDFTYPE:
			{
				int idx = m_pCurControlDlg->GetComboBox(IDC_COMBOBOX_BRDFTYPE)->GetSelectedIndex();				
				switch(idx)
				{
				case LAFORTUNE:
					m_curBRDFType = LAFORTUNE;
					break;
				case WARD:
					m_curBRDFType = WARD;
					break;
				case SV_LAFORTUNE:
					m_curBRDFType = SV_LAFORTUNE;
					break;
				case DIFFUSE:
					m_curBRDFType = DIFFUSE;
					break;
				case MIRROR:
					m_curBRDFType = MIRROR;
					break;
				}
				UpdateGUIComponents(m_pCurControlDlg);
			}
			break;
	}

	if (m_curBRDFType == BRDFType::LAFORTUNE)
	{
		switch( nControlID )
		{
		case IDC_SLIDER_CXY:
			{
			}
			break;
		}
	}
	
}

void CRenderBRDFModels::UpdateLightingEnvironment(CBaseCamera* pViewCamera)
{
	unsigned int iOrder = m_pEMLighting->GetOrderNum();
	float fSum[3][D3DXSH_MAXORDER*D3DXSH_MAXORDER];
	ZeroMemory( fSum, 3 * iOrder* iOrder * sizeof( float ) );

	D3DXMATRIX mWorldInv;
	CModelViewerCamera* pCurCamera = static_cast<CModelViewerCamera*>(pViewCamera);
	D3DXMatrixInverse( &mWorldInv, NULL, pCurCamera->GetWorldMatrix() );

	float fLightProbe1Rot[3][D3DXSH_MAXORDER*D3DXSH_MAXORDER];
	D3DXSHRotate( fLightProbe1Rot[0], iOrder, &mWorldInv, m_pEMLighting->GetSHData(0));
	D3DXSHRotate( fLightProbe1Rot[1], iOrder, &mWorldInv, m_pEMLighting->GetSHData(1) );
	D3DXSHRotate( fLightProbe1Rot[2], iOrder, &mWorldInv, m_pEMLighting->GetSHData(2) );
	D3DXSHAdd( fSum[0], iOrder, fSum[0], fLightProbe1Rot[0] );
	D3DXSHAdd( fSum[1], iOrder, fSum[1], fLightProbe1Rot[1] );
	D3DXSHAdd( fSum[2], iOrder, fSum[2], fLightProbe1Rot[2] );

	ComputeSHIrradEnvMapConstants(m_pEffect, fSum[0], fSum[1], fSum[2] );
}

void CRenderBRDFModels::ComputeSHIrradEnvMapConstants(ID3DX11Effect* pSHIrradEnvMapEffect, float* pSHCoeffsRed, float* pSHCoeffsGreen, float* pSHCoeffsBlue )
{
	HRESULT hr;

	float* fLight[3] = { pSHCoeffsRed, pSHCoeffsGreen, pSHCoeffsBlue };

	// Lighting environment coefficients
	D3DXVECTOR4 vCoefficients[3];

	// These constants are described in the article by Peter-Pike Sloan titled 
	// "Efficient Evaluation of Irradiance Environment Maps" in the book 
	// "ShaderX 2 - Shader Programming Tips and Tricks" by Wolfgang F. Engel.
	static const float s_fSqrtPI = ( ( float )sqrtf( D3DX_PI ) );
	const float fC0 = 1.0f / ( 2.0f * s_fSqrtPI );
	const float fC1 = ( float )sqrt( 3.0f ) / ( 3.0f * s_fSqrtPI );
	const float fC2 = ( float )sqrt( 15.0f ) / ( 8.0f * s_fSqrtPI );
	const float fC3 = ( float )sqrt( 5.0f ) / ( 16.0f * s_fSqrtPI );
	const float fC4 = 0.5f * fC2;

	int iChannel;
	for( iChannel = 0; iChannel < 3; iChannel++ )
	{
		vCoefficients[iChannel].x = -fC1 * fLight[iChannel][3];
		vCoefficients[iChannel].y = -fC1 * fLight[iChannel][1];
		vCoefficients[iChannel].z = fC1 * fLight[iChannel][2];
		vCoefficients[iChannel].w = fC0 * fLight[iChannel][0] - fC3 * fLight[iChannel][6];
	}

	V( pSHIrradEnvMapEffect->GetVariableByName("cAr")->AsVector()->SetFloatVector((float*)(&vCoefficients[0]) ) );
	V( pSHIrradEnvMapEffect->GetVariableByName("cAg")->AsVector()->SetFloatVector((float*)(&vCoefficients[1]) ) );
	V( pSHIrradEnvMapEffect->GetVariableByName("cAb")->AsVector()->SetFloatVector((float*)(&vCoefficients[2]) ) );
	
	for( iChannel = 0; iChannel < 3; iChannel++ )
	{
		vCoefficients[iChannel].x = fC2 * fLight[iChannel][4];
		vCoefficients[iChannel].y = -fC2 * fLight[iChannel][5];
		vCoefficients[iChannel].z = 3.0f * fC3 * fLight[iChannel][6];
		vCoefficients[iChannel].w = -fC2 * fLight[iChannel][7];
	}

	V( pSHIrradEnvMapEffect->GetVariableByName("cBr")->AsVector()->SetFloatVector((float*)(&vCoefficients[0]) ) );
	V( pSHIrradEnvMapEffect->GetVariableByName("cBg")->AsVector()->SetFloatVector((float*)(&vCoefficients[1]) ) );
	V( pSHIrradEnvMapEffect->GetVariableByName("cBb")->AsVector()->SetFloatVector((float*)(&vCoefficients[2]) ) );

	vCoefficients[0].x = fC4 * fLight[0][8];
	vCoefficients[0].y = fC4 * fLight[1][8];
	vCoefficients[0].z = fC4 * fLight[2][8];
	vCoefficients[0].w = 1.0f;

	V( pSHIrradEnvMapEffect->GetVariableByName("cC")->AsVector()->SetFloatVector((float*)(&vCoefficients[0]) ) );	
}

